﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace ColorWheelNET.Renderers
{
    internal static class ValueWheel
    {
        #region Constants

        const int _DISPLAY_BUFFER = 2;
        const double _DEGREES_IN_CIRCLE = 360;

        static double _RADIANS_MULTIPLIER = (Math.PI / ((float)180));
        static double _ANGLE_STEP = 5;
        private static double _DEFAULT_VALUE = 1.0;
        private static double _DEFAULT_SATURATION = 1.0;

        private static int _WHITE_CLICK_TOLERANCE = 23;

        #endregion

        /// <summary>
        /// all the Draw Wheel calls go here!
        /// </summary>
        /// <param name="wheel"></param>
        /// <param name="graphics"></param>
        internal static void DrawWheel(CWheel wheel, Graphics graphics)
        {
            if (wheel.Points == null || wheel.Colors == null) { return; }

            using (PathGradientBrush b = new PathGradientBrush(wheel.Points, WrapMode.Clamp))
            {
                b.CenterPoint = wheel.WheelCenter;
                b.CenterColor = Color.Black;
                b.SurroundColors = wheel.Colors;

                //Draw the wheel
                graphics.FillPie(b, wheel.WheelRectangle, 0, 360);
            }
        }

        /// <summary>
        /// We need to draw a small box around the SelectedColor's position in the wheel
        /// </summary>
        /// <param name="wheel"></param>
        /// <param name="graphics"></param>
        internal static void DrawSelector(CWheel wheel, Graphics graphics)
        {
            if (!wheel.ShowSelector) { return; }

            if (wheel.SelectedPoint.IsEmpty)
            {
                CalculateSelectedPoint(wheel);
                //if we failed to calculate it give up
                if (wheel.SelectedPoint.IsEmpty) { return; }
            }

            using (SolidBrush b = new SolidBrush(wheel.SelectorColor))
            {
                Rectangle selRec = new Rectangle(wheel.SelectedPoint.X - _DISPLAY_BUFFER, wheel.SelectedPoint.Y - _DISPLAY_BUFFER, _DISPLAY_BUFFER * 2, _DISPLAY_BUFFER * 2);

                Pen p = new Pen(b);
                p.Width = 2;

                graphics.DrawEllipse(p, selRec);
            }
        }

        /// <summary>
        /// Calculates the Selected point on the wheel
        /// </summary>
        /// <param name="wheel"></param>
        internal static void CalculateSelectedPoint(CWheel wheel)
        {
            if (wheel.Color.IsEmpty)
            {
                wheel.SelectedPoint = Point.Empty;
                return;
            }
            if (wheel.Color == Color.White || wheel.Color == Color.Black || (wheel.Color.R == wheel.Color.B && wheel.Color.R == wheel.Color.G))
            {
                wheel.SelectedPoint = new Point((int)wheel.WheelCenter.X, (int)wheel.WheelCenter.Y);
                return;
            }

            double hue, saturation, value;
            ColorHelper.HSVFromRGB(wheel.Color, out hue, out saturation, out value);

            //Angle from hue
            double angle = hue; //it seems Color.GetHue() returns the degrees
            double radians = angle * _RADIANS_MULTIPLIER - wheel.SegmentRotation; //compensate for the segment rotation

            Rectangle wrec = wheel.WheelRectangle;
            float radius = (float)((float)wrec.Width / 2f);


            double distanceFromCenter = value * radius;
            double dX = distanceFromCenter * Math.Cos(radians);
            double dY = distanceFromCenter * Math.Sin(radians);

            wheel.SelectedPoint = new Point((int)wheel.WheelCenter.X + (int)dX, (int)wheel.WheelCenter.Y - (int)dY);
        }

        /// <summary>
        /// Calculates the Color Group based on a point
        /// </summary>
        /// <param name="wheel"></param>
        /// <param name="pt"></param>
        internal static void CalculateColorGroup(CWheel wheel, Point pt)
        {
            wheel.ColorGroup = Color.Empty;

            Rectangle wrec = wheel.WheelRectangle;
            Color color = wheel.Color;

            if (color == Color.White)
            {
                wheel.ColorGroup = Color.White;
                return;
            }

            if (wheel.Colors == null) { return; }

            if (!wrec.Contains(pt)) { return; }


            float radius = (float)((float)wrec.Width / 2f);

            //Calculate the Distance
            double dX = pt.X - wheel.WheelCenter.X;
            double dY = pt.Y - wheel.WheelCenter.Y;
            double distanceFromCenter = Math.Sqrt((dX * dX) + (dY * dY));


            //if the distance of the clicked point is beyond the radius, then we clicked just past the circle border
            if (distanceFromCenter > radius) { return; }

            if (distanceFromCenter < (radius / _WHITE_CLICK_TOLERANCE))
            {
                wheel.ColorGroup = Color.White;
                return;
            }

            //Calculate the angle 
            double angle = Math.Atan2(dY, dX) / _RADIANS_MULTIPLIER;
            double radians = angle * _RADIANS_MULTIPLIER - wheel.SegmentRotation;


            //Let's compensate for the user defined radian rotation
            double trueAngle = radians * 180 / Math.PI;

            if (trueAngle < 0)
            {
                trueAngle = _DEGREES_IN_CIRCLE - trueAngle;
            }

            //Standardize it to a max of 360 degrees
            while (trueAngle > _DEGREES_IN_CIRCLE)
            {
                trueAngle -= _DEGREES_IN_CIRCLE;
            }

            //Calculate the color segment index in our this.Colors array
            int segment = (int)(trueAngle / _ANGLE_STEP);

            //For some reason the color segment isn't in the array
            if (segment < 0 || segment > wheel.Colors.Length) { return; }
            wheel.ColorGroup = wheel.Colors[segment];
        }


        /// <summary>
        /// Tests for a color based on a point
        /// </summary>
        /// <param name="wheel"></param>
        /// <param name="pt"></param>
        internal static void ColorHitTest(CWheel wheel, Point pt)
        {
            Rectangle wrec = wheel.WheelRectangle;
            Color color = wheel.Color;

            if (wheel.Colors == null) { return; }

            if (!wrec.Contains(pt)) { return; }


            float radius = (float)((float)wrec.Width / 2f);

            //Calculate the Distance
            double dX = pt.X - wheel.WheelCenter.X;
            double dY = pt.Y - wheel.WheelCenter.Y;
            double distanceFromCenter = Math.Sqrt((dX * dX) + (dY * dY));


            //if the distance of the clicked point is beyond the radius, then we clicked just past the circle border
            if (distanceFromCenter > radius) { return; }

            if (distanceFromCenter < (radius / _WHITE_CLICK_TOLERANCE))
            {
                wheel.SetSelectedColor(Color.Black, pt, Color.White);
                return;
            }

            //Calculate the angle 
            double angle = Math.Atan2(dY, dX) / _RADIANS_MULTIPLIER;
            double radians = angle * _RADIANS_MULTIPLIER - wheel.SegmentRotation;


            //Let's compensate for the user defined radian rotation
            double trueAngle = radians * 180 / Math.PI; ;

         //   if (trueAngle < 0)
         //   {
                trueAngle = _DEGREES_IN_CIRCLE - trueAngle;
       //     }

            //Standardize it to a max of 360 degrees
            while (trueAngle > _DEGREES_IN_CIRCLE)
            {
                trueAngle -= _DEGREES_IN_CIRCLE;
            }

            //Calculate the color segment index in our this.Colors array
            int segment = (int)(trueAngle / _ANGLE_STEP);

            //For some reason the color segment isn't in the array
            if (segment < 0 || segment > wheel.Colors.Length) { return; }

            double saturation = _DEFAULT_SATURATION;// distanceFromCenter / radius;
            double value = distanceFromCenter / radius; //saturation >= _DEFAULT_LIGHTNESS ? _DEFAULT_LIGHTNESS : _LOW_SATURATION_LIGHTNESS_STANDARD - saturation;
            double hue = trueAngle;// / _DEGREES_IN_CIRCLE;

            //This is our true color.
            color = ColorHelper.ColorFromHSV(hue, saturation, value); //this.Colors[segment];


            //Grab the true HSL color that was clicked and assign it

            wheel.SetSelectedColor(color, pt, wheel.Colors[segment]);
        }


        /// <summary>
        /// Calculates the Layout
        /// </summary>
        /// <param name="wheel"></param>
        internal static void CalculateLayout(CWheel wheel)
        {
            List<PointF> points = new List<PointF>();
            List<Color> colors = new List<Color>();

            Rectangle wRec = wheel.WheelRectangle;
            float radius = (float)(wRec.Width / 2);

            Color lastColor = Color.White;


            for (double angle = 0; angle < _DEGREES_IN_CIRCLE; angle += _ANGLE_STEP)
            {
                double radians = angle * _RADIANS_MULTIPLIER - wheel.SegmentRotation;
                points.Add(new PointF((float)(wheel.WheelCenter.X + (Math.Cos(radians) * radius)), (float)(wheel.WheelCenter.Y - Math.Sin(radians) * radius)));

                try
                {
                    lastColor = ColorHelper.ColorFromHSV(angle, _DEFAULT_SATURATION, _DEFAULT_VALUE);
                }
                finally
                {
                    colors.Add(lastColor);
                }
            }

            wheel.Points = points.ToArray();
            wheel.Colors = colors.ToArray();


            if (wheel.SelectedPoint.IsEmpty && wheel.OnCalculateSelectedPoint != null)
            {
                wheel.OnCalculateSelectedPoint(wheel);
            }

            if (wheel.SelectorColor.IsEmpty && wheel.OnCalculateSelectorColor != null)
            {
                wheel.OnCalculateSelectorColor(wheel);
            }
        }


        internal static void CalculateSelectorColor(CWheel wheel)
        {
            if (wheel.Color.IsEmpty)
            {
                wheel.SelectorColor = Color.White;
                return;
            }

            int r = wheel.Color.R;
            int g = wheel.Color.G;
            int b = wheel.Color.B;

            if (r > 200 && g > 200 && b > 200 || wheel.Color == Color.White || (wheel.Color.R == wheel.Color.B && wheel.Color.B == wheel.Color.G))
            {
                wheel.SelectorColor = Color.Thistle;
            }
            else if (r > b)
            {
                if (g > b)
                {
                    if (g < 80)
                    {
                        wheel.SelectorColor = Color.LightGray;
                    }
                    else
                    {
                        wheel.SelectorColor = Color.PaleGoldenrod;
                    }
                }
                else
                {
                    wheel.SelectorColor = Color.Snow;
                }
            }
            else if (b > g)
            {
                wheel.SelectorColor = Color.Gold;
            }
            else if (g > r)
            {
                if (b > 110)
                {
                    wheel.SelectorColor = Color.Goldenrod;
                }
                else
                {
                    wheel.SelectorColor = Color.GhostWhite;
                }
            }
            else
            {
                wheel.SelectorColor = Color.WhiteSmoke;
            }

        }


    }
}
